#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import struct
import base64
import random


class simpleEncode:
    saltList=[\
 27169, 40118, 15746, 46781, 24687, 24123, 47311, 59611, 18160, 45475, 50579, 43737, 4781, \
 25186, 20206, 17980, 45823, 59517, 52886, 18223, 40892, 39343, 44800, 63708, 29754, 35217, \
 40416, 11340, 27150, 53350, 47583, 28213, 10210, 50380, 51411, 58484, 39959, 17223, 20773, \
 35736, 10074, 30263, 32031, 61941, 58707, 34553, 39676, 31879, 35627, 22845, 14821, 39610, \
 47017, 14078, 44191, 26197, 53273, 6856, 40221, 27320, 48440, 29960, 5534, 41934, 45138, \
 32004, 20489, 929, 46244, 50484, 23904, 35713, 10679, 34919, 32634, 25969, 60846, 63039, \
 21902, 29667, 57289, 24611, 9723, 25560, 49950, 21004, 24033, 17802, 56595, 47018, 37543, \
 18319, 51295, 47608, 38813, 41381, 9342, 14593, 3305, 40749, 28682, 14670, 22975, 50454, \
 44675, 49047, 31683, 42053, 37760, 18555, 15989, 6479, 45929, 26445, 6048, 25906, 15145, \
4550, 33112, 15218, 31667, 46126, 43391, 56453, 57349, 39364, 7036, 50021\
]
    saltListLen=128
        
    def __init__(self):
        pass
          
          
    def ROTATE_LEFT(self, data, bitLen, n):
        nT1=(data<<n) &0xFF
        nT2=data>>(bitLen-n)
        result=nT1|nT2
        return result
        
    def decodeWithSeedList(self, dataList, seedList):
        result=bytearray()
        nLen=len(dataList)
        nSeed=seedList[0]*256+seedList[1]
        j=0
        k=0
        m = nSeed % self.saltListLen
        for i in range(0, nLen):
            nAscII=dataList[i]
            nSalt=self.saltList[m] & 0xFF
            n=self.saltList[m] & 0x7
            nSalt=self.ROTATE_LEFT(nSalt, 8, n)
            
            nXorSeed=seedList[j] ^ nSalt
            nT1=nAscII ^ nXorSeed
            result.append(nT1)
            k+=1
            j+=1
            if j>=2:
                j=0
            m+=1
            if m>=self.saltListLen:
                m=0
        return result
    
        
    def decodeWOSeed(self, bytesIn):
        seedList=bytesIn[0:2]
        dataList=bytesIn[2:]
        return self.decodeWithSeedList(dataList, seedList)


    def encodeWOSeed(self, bytesIn):
        seedList=[0, 0]
        seedList[0]=random.randint(0, 255)
        seedList[1]=random.randint(0, 255)
        dataList=[]
        for byte in bytesIn:
            dataList.append(int(byte.encode("hex"), 16))
        dataList=self.decodeWithSeedList(dataList, seedList)
        result=bytearray(seedList)+dataList
        return result


class LoRAMACCoder:
    #const definition
    LORA_CMD_NULL_CMD = 0 #空命令,无需解释和执行
    LORA_CMD_EXTEND_CMD = 1    #扩展命令,用于避免两个字节的命令类型不够的情况
    LORA_CMD_ACK_SUCCESS = 2 #上次命令的应答，成功
    LORA_CMD_ACK_FAIL = 3 #上次命令的应答，失败
    
    LORA_CMD_REGISTRATION_CLEANUP =  9   #服务登记清除，所有设备需要重新登记 
    LORA_CMD_REGISTRATION_REQUEST =  10   #服务登记请求，一个Terminal向router申请加入LoRa网络,同时会上传自己的唯一号 devID(IMEI),MAID(制造商代号)和PID(项目代号)
    LORA_CMD_REGISTRATION_FEEDBACK =  11   #服务登记回馈，一个Terminal向router申请加入LoRa网络,同时会下发内部地址 
    LORA_CMD_REGISTRATION_REPORT =  12   #服务登记状态报告，一个router向上级报告加入网络分配情况 
    LORA_CMD_ADDR_REQUEST = 13   #内部地址分配请求,一般是一个Terminal加入一个LoRa后,需要新分配或者获取已经分配的内部地址。
    LORA_CMD_ADDR_FEEDBACK = 14   #内部地址分配回馈,返回新分配或者获取已经分配的内部地址。
    LORA_CMD_SYNC_CMD = 15  #router 同步,Terminal发送的开始时间,以及,分组和顺序
    LORA_CMD_SYNC_TIME = 16   # router 同步时间
    LORA_CMD_SYNC_RANDOM = 17  #router 给出自由time slot,等需要发送设备请求，注册等信息的设备。 
    LORA_CMD_VERIFY_REQUEST = 18 #要求验证对方设备的真伪，需要发送一组随机数,至少是8个字节的数据,
    LORA_CMD_VERIFY_FEEDBACK = 19 #验证反馈,回送对方8个字节的数据,和对应的MD5或者其他方式的hash结果,结果不少于8个字节。
    LORA_CMD_DHCP_POOL_REQUEST = 20 #host 主设备向上级服务器申请一个内部地址池
    LORA_CMD_DHCP_POOL_FEEDBACK = 21 #上级服务器对 host 主设备向申请一个内部地址池请求的回应,给一个段落(开始和结束)
    LORA_CMD_ADDR_CHANGE = 22 #对特定设备内部地址变更的通知,是对LORA_CMD_ADDR_REQUEST/LORA_CMD_ADDR_FEEDBACK的增强,格式和LORA_CMD_ADDR_FEEDBACK相同
    LORA_CMD_FREQ_CHANGE = 23 #对特定设备通信频率变更的通知,
    LORA_CMD_ADDR_REJECT = 24 #对特定设备拒绝服务的通知,
    LORA_CMD_DATA_REPORT = 25 #数据上报消息，或者是状态上报消息

    #application cmd list
    LORA_CMD_APP_EXTEND_CMD = 256 #应用扩展命令集合
    LORA_CMD_APP_FMRADIO = 257 #FM Radion APPLICATION CMD 
    LORA_CMD_APP_DIANJI = 258 #电机控制应用 APPLICATION CMD 
 
 
    #公共常量定义
    LORA_MAC_ADDRESS_LEN =4
    headerLength=12
    len_stDataCMD_00 = 14
    soureAddrPosition=0
    destAddrPosition=4
    seqPosition=8
    fillPosition=9
    CMDPosition=10
    
    #FM Radio 部分常量定义
    # 频率2=byte，开关(1bit)+设备有效(1bit)+校园(1bit)+音量(5bit)=1 byte,背景灯 1 byte,静噪 1bit+ 阈值+5bit =1 byte 
    len_APP_stFMCommand=4
    len_APP_stFMStat=8
    CONST_APP_FM_STAT_STRING_LENGTH = 6
    CONST_APP_FM_STAT_STRING_SWITCH_MASK = 0x80
    CONST_APP_FM_STAT_STRING_VALID_MASK = 0x40
    CONST_APP_FM_STAT_STRING_CAMPUS_MASK = 0x20
    CONST_APP_FM_STAT_STRING_VOL_MASK = 0x1F
    CONST_APP_FM_STAT_STRING_DSP_MASK = 0x80
    CONST_APP_FM_STAT_STRING_SN_MASK = 0x1F
    
    FM_SET_FRE=0
    FM_FRE_DOWN=1
    FM_FRE_UP=2
    FM_PLAY_PAUSE=3
    FM_SET_VOL=4
    FM_VOL_DOWN=5
    FM_VOL_UP=6
    FM_SET_BANK=7
    FM_SET_CAMPUS=8
    FM_SET_DSP=9
    FM_SET_SN_THR=10
    FM_RESET=11
    FM_STATUS=12
    FM_SCAN=13
    FM_SCAND=14
    FM_SCANU=15
    FM_SCAMSTOP=16
    FM_CHNUM=17
    FM_CHDOWN=18
    FM_CHUP=19
    FM_SET_ALL=20
    FM_STAT_REPORT=21
    FM_SAVE_FREQ=22
    FM_OP_SAVE_REEQ=23
    
    APP_FM_command_Position=0
    APP_FM_flag_Position=1
    APP_FM_param_Position=2
    APP_FM_statString_Position=2
    
    #C struct format string
    fmt_stDataCMD_header                  = "BBBBBBBBBBh"
    fmt_stDataCMD_00                        = "BBBBBBBBBBhh"
    fmt_stDataCMD_RegistrationRequest = "LHHBBBBBBBB"
    fmt_stDataCMD_RegistrationFeedbackCMD  = "LBBBBBBBB"
    fmt_stDataCMD_verifyFeedbackCMD  = "LHHBBBBBBBB"
    fmt_stDataCMD_synCMD                 =  "BBBBBBBBBBBB"
    fmt_stDataCMD_stDataCMD_APP       = "BBBBBBBBBBBBBBBB"
    fmt_stDataCMD_frequencyChange     = "h"
    
    
    #FM Radio data
    fmt_APP_stFMCommand = "BBh"
    fmt_APP_stFMStat        = "BBBBBBBB"    
    
    #others
    headerSet={}
    
    def decode_header(self, dataIn):
        result={"CMD":0, "sourceAddr":"", "destAddr":"","seq":"", "fill":"" }
        tempData=dataIn[0:self.len_stDataCMD_00]
        set1=struct.unpack(self.fmt_stDataCMD_00, tempData)
        
        cmd=set1[self.CMDPosition]
        result["CMD"]=str(cmd)
        
        aList=[]
        for i in range(self.soureAddrPosition, self.soureAddrPosition+self.LORA_MAC_ADDRESS_LEN):
            aList.append(str(set1[i]))
            aList.append(".")
        aList.pop()
        result["sourceAddr"]="".join(aList)
        
        aList=[]
        for i in range(self.destAddrPosition, self.destAddrPosition+self.LORA_MAC_ADDRESS_LEN):
            aList.append(str(set1[i]))
            aList.append(".")
        aList.pop()
        result["destAddr"]="".join(aList)
        
        result["seq"]=str(set1[self.seqPosition])
        result["fill"]=str(set1[self.fillPosition])
        return result


    
    def decode(self, dataIn):
        self.headerSet=self.decode_header(dataIn)
        cmd=int(self.headerSet["CMD"])
        appSet={}
        
        if cmd == self.LORA_CMD_NULL_CMD:
            pass
        elif cmd == self.LORA_CMD_EXTEND_CMD:
            self.decode_cmd_extend_cmd(dataIn);
        elif cmd == self.LORA_CMD_ACK_SUCCESS:
            pass
        elif cmd == self.LORA_CMD_ACK_FAIL:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_CLEANUP:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REQUEST:
            appSet=self.decode_cmd_registration_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_FEEDBACK:
            appSet= self.decode_cmd_registration_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REPORT:
            appSet= self.decode_cmd_registration_report(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_REQUEST:
            appSet= self.decode_cmd_addr_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_FEEDBACK:
            appSet= self.decode_cmd_addr_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_SYNC_CMD:
            pass
        elif cmd == self.LORA_CMD_SYNC_TIME:
            pass
        elif cmd == self.LORA_CMD_SYNC_RANDOM:
            pass
        elif cmd == self.LORA_CMD_VERIFY_REQUEST:
            appSet= self.decode_cmd_verify_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_VERIFY_FEEDBACK:
            appSet= self.decode_cmd_verify_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_CHANGE:
            pass
        elif cmd == self.LORA_CMD_DATA_REPORT:
            pass
        elif cmd == self.LORA_CMD_APP_EXTEND_CMD:
            pass
        elif cmd == self.LORA_CMD_APP_FMRADIO:
            appSet=self.decode_cmd_app_fmradio(dataIn)
            pass
        elif cmd == self.LORA_CMD_APP_DIANJI:
            pass
        result=dict(self.headerSet, **appSet)
        return result
        
    def decode_cmd_extend_cmd(self, dataIn):
        result={}
        return result
        
    def decode_cmd_registration_request(self, dataIn):
        result={}
        return result

    def decode_cmd_registration_feedback(self, dataIn):
        result={}
        return result
        
    def decode_cmd_registration_report(self, dataIn):
        result={}
        return result

    def decode_cmd_addr_request(self, dataIn):
        result={}
        return result

    def decode_cmd_addr_feedback(self, dataIn):
        result={}
        return result

    def decode_cmd_verify_request(self, dataIn):
        result={}
        return result

    def decode_cmd_verify_feedback(self, dataIn):
        result={}
        return result

        
    def decode_cmd_app_fmradio(self, dataIn):
        result={}
        appData=dataIn[self.headerLength:]
        tempData=appData[0:self.len_APP_stFMCommand]
        set1=struct.unpack(self.fmt_APP_stFMCommand, tempData)
        command=set1[self.APP_FM_command_Position]
        if command==self.FM_STAT_REPORT:
            tempData=appData[0:self.len_APP_stFMStat]
            set1=struct.unpack(self.fmt_APP_stFMStat, tempData)
            flag=set1[self.APP_FM_flag_Position]
            statString=set1[self.APP_FM_statString_Position:]
            #
            frequency=statString[1]<<8
            frequency+=statString[0]
            nT1=statString[2]
            stat=(nT1 & self.CONST_APP_FM_STAT_STRING_SWITCH_MASK)>>7
            err = (nT1 & self.CONST_APP_FM_STAT_STRING_VALID_MASK) >> 6
            campus = (nT1 & self.CONST_APP_FM_STAT_STRING_CAMPUS_MASK) >> 5
            vol = nT1 & self.CONST_APP_FM_STAT_STRING_VOL_MASK
            blk = statString[3]
            nT1 = statString[4]
            dsp = (nT1 & self.CONST_APP_FM_STAT_STRING_DSP_MASK) >> 7
            snthr = nT1 & self.CONST_APP_FM_STAT_STRING_SN_MASK

            result["appCMD"]=str(command)
            result["STAT"]=str(stat)
            result["freq"]=str(frequency)
            result["err"]=str(err)
            result["campus"]=str(campus)
            result["vol"]=str(vol)
            result["BLK"]=str(blk)
            result["DSP"]=str(dsp)
            result["SN"]=str(snthr)
            
            pass
        else:
            pass
        pass
        return result

    def encode(self, dataIn):
        headerBytes=self.encode_header(dataIn)
        cmd=int(dataIn["CMD"])
        
        if cmd == self.LORA_CMD_NULL_CMD:
            pass
        elif cmd == self.LORA_CMD_EXTEND_CMD:
            self.encode_cmd_extend_cmd(dataIn);
        elif cmd == self.LORA_CMD_ACK_SUCCESS:
            pass
        elif cmd == self.LORA_CMD_ACK_FAIL:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_CLEANUP:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REQUEST:
            appBytes=self.encode_cmd_registration_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_FEEDBACK:
            appBytes= self.encode_cmd_registration_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REPORT:
            appBytes= self.encode_cmd_registration_report(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_REQUEST:
            appBytes= self.encode_cmd_addr_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_FEEDBACK:
            appBytes= self.encode_cmd_addr_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_SYNC_CMD:
            pass
        elif cmd == self.LORA_CMD_SYNC_TIME:
            pass
        elif cmd == self.LORA_CMD_SYNC_RANDOM:
            pass
        elif cmd == self.LORA_CMD_VERIFY_REQUEST:
            appBytes= self.encode_cmd_verify_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_VERIFY_FEEDBACK:
            appBytes= self.encode_cmd_verify_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_CHANGE:
            pass
        elif cmd == self.LORA_CMD_DATA_REPORT:
            pass
        elif cmd == self.LORA_CMD_APP_EXTEND_CMD:
            pass
        elif cmd == self.LORA_CMD_APP_FMRADIO:
            appBytes=self.encode_cmd_app_fmradio(dataIn)
            pass
        elif cmd == self.LORA_CMD_APP_DIANJI:
            pass
        result=headerBytes+appBytes
        return result

    def encode_header(self, dataIn):
        result=bytearray()
        cmd=int(dataIn["CMD"])
        aList=dataIn["from"].split(".")
        sourceAddrList=map(int,  aList)        
        aList=dataIn["to"].split(".")
        destAddrList=map(int,  aList) 
        seq=int(dataIn["seq"])
        if "fill" in dataIn:
            fill=int(dataIn["fill"])
        else:
            fill=0
        
        result=struct.pack(self.fmt_stDataCMD_header, sourceAddrList[0], sourceAddrList[1], sourceAddrList[2], sourceAddrList[3], 
        destAddrList[0], destAddrList[1], destAddrList[2], destAddrList[3], seq, fill, cmd)
        
        return result


    def encode_cmd_extend_cmd(self, dataIn):
        result=bytearray()
        return result
        
    def encode_cmd_registration_request(self, dataIn):
        result=bytearray()
        return result

    def encode_cmd_registration_feedback(self, dataIn):
        result=bytearray()
        devID=int(dataIn["devID"])
        addrList=dataIn["addr"].split(".")
        addrList=map(int, addrList)
        valList=[0, 0, 0, 0]
        valList[0]=int(dataIn["val"][0:2], 16)
        valList[1]=int(dataIn["val"][2:4], 16)
        valList[2]=int(dataIn["val"][4:6], 16)
        valList[3]=int(dataIn["val"][6:8], 16)
        result=struct.pack(self.fmt_stDataCMD_RegistrationFeedbackCMD, devID, addrList[0], addrList[1], addrList[2], addrList[3], 
        valList[0], valList[1], valList[2], valList[3]) 
        return result
        
    def encode_cmd_registration_report(self, dataIn):
        result=bytearray()
        return result

    def encode_cmd_addr_request(self, dataIn):
        result=bytearray()
        return result

    def encode_cmd_addr_feedback(self, dataIn):
        result=bytearray()
        return result

    def encode_cmd_verify_request(self, dataIn):
        result=bytearray()
        return result

    def encode_cmd_verify_feedback(self, dataIn):
        result=bytearray()
        return result

        
    def encode_cmd_app_fmradio(self, dataIn):
        result=bytearray()
        command=int(dataIn["appCMD"])
        flag=1
        param=int(dataIn["param"])
        result=struct.pack(self.fmt_APP_stFMCommand, command, flag, param)

        return result


def to_decode_sub_data(strIn):
    result={}
    coder=simpleEncode()
    bytes=bytearray()
    strOut=base64.b64decode(strIn)
    bytes.extend(strOut)
    byteResult=coder.decodeWOSeed(bytes)
    macCoder=LoRAMACCoder()
    result=macCoder.decode(byteResult)
    return result
    

def to_decode_data(msg):    
    result={}
    aSet=json.loads(msg)
    aList=aSet["data"]
    
    #print aList
    newList=[]
    for data in aList:
        set1=to_decode_sub_data(data)
        newList.append(set1)
    nLen=len(newList)
    result["pushID"]=aSet["pushID"]
    result["len"]=str(nLen)
    result["data"]=newList
    return result

def to_encode_sub_data(data):
    result=""
    macCoder=LoRAMACCoder()
    bytes=macCoder.encode(data)
    coder=simpleEncode()
    byteResult=coder.encodeWOSeed(bytes)
    result=base64.b64encode(byteResult)
    return result


def to_encode_data(dataSet):
    result={}
    aList=dataSet["devTodo"]
    newList=[]
    for data  in aList:
        msg=to_encode_sub_data(data)
        newList.append(msg)
    nLen=len(newList)
#    result["pushID"]=dataSet["pushID"]
#    result["len"]=str(nLen)
    dataSet["devTodo"]=newList
    result=dataSet
    return result
        

def main():
    print
    msg='{"data":["SmMv49m5D9eBfNHjkMz4sWfADTvxO4P+wdz6wwak","6BjAkwjZqykHv1HUd152u8iDDoltpnLrJ9uxv1Ty","aLT7o6cbmtTLDWG8zgY09P70rf0Ct7MHgVbDK8ur","XbTOo5Ibr9T+DVW8+wYB9Mv0mP03t4YHtFb2K/6r","3Xy9mBrVX3nXykWlO6M4YcXM5/9svyLTeOsWq92x","BI8H2KsvU+zTjudnJm7VDBljLQ7CEUMEpyuc5p8A","jOKf76R0k8+QKL9KDTg3RdpIoyDUU9QrEWkzUiyu","bUpEAMbQDv3+Up6S64u7YHHjMYfIst7+8ki+70XZ"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"47382","seed2":"9a5e6f5b","DateTime":"20170509042754"}'
    msg='{"data":["XM4/ec/SQ77bDxHkxWWaCXc1YHDJzI9rdF2DS0AF","Kfh025ZNSRzuVeZVIk6LIUolVu+/S5xxkjvWV4xv","xkrvAG3Qpf1VUpWSQIsQYNrjmodjsnX+WUgV7+7Z","TZ5ikMZXE3HGPGIGJQTXIFNuDVceOfF0HiGCNH9W","Hzp8Kic3eCFWe4d5VHhqOUCKedK+pbwljCPA4pj6","OEaY7ePwEQyT2BJZql7ynjuFdGaq7OuBl76L8qdE","DEl/CO7nU0IT252W1c/ZiqLSKZDB5vT6uNYOmqlh","ZSPiAMaCPeL+rVy5l+2zekacAHrQ7M8RrYDdG22E"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"19816","seed2":"c737e5ac","DateTime":"20170509044637"}'
    msg='{"data":["LALmHZveC3TcHraxmIqCwVeuBp/t1SzPxCHMxq+b","MDMayI/5Wqn4kY0LOZWWevQi/HN5UzPog9rSmK+Q","Z1kNIqDzX0LTx2GKw3DhhmZGI03F2bz3wIvO+a8L","oDHHJUpn31YKAmuSGQi9lpd7bynpe+NRo+oT2EKa","c9zcJMBoEyFfhllPplkigsN0+wffew9nmAZrDH8U","Tr2Gsv3xzgcN9gzdTGfoVCgVXhRbLldiljqOfmQl","EauvMeJkLQ1J6Pv4pGT2DHcIoZMbDAzwGqsYgAbK","N+5Z4eVCV45lXuAnq2SdXhOhf0h/Qz1ZgDfQMMfz"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"28817","seed2":"54783074","DateTime":"20170509044756"}'
    dataSet=to_decode_data(msg)
    print dataSet
    print
    dataIn={"pushID":"255.255.255.255","len":"1","devTodo":[ {"CMD":11, "from":"10.0.0.1", "to": "255.255.255.255", "seq":"12","devID":"12345670","addr":"192.168.0.1","val":"BBCCDDEEFF"}]}
    dataSet=to_encode_data(dataIn)
    print dataSet
    print
    dataIn={"pushID":"192.168.0.1","len":"1","devTodo":[ {"CMD":"257", "from":"10.0.0.1", "to": "192.168.255.255", "seq":"12","appCMD":"0","param":"1039"}]}
    dataSet=to_encode_data(dataIn)
    print dataSet
    print
    dataIn={"pushID":"192.168.0.1","len":"1","devTodo":[ {"CMD":257, "from":" 10.0.0.1", "to": "192.168.255.255", "seq":"12","appCMD":"4","param":"19"}]}
    dataSet=to_encode_data(dataIn)
    print dataSet
    print
    

if __name__ == "__main__":
    main()
